#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <bps/bps.h>
#include <bps/navigator.h>
#include <bps/screen.h>
#include <bps/geolocation.h>
#include <bps/dialog.h>
#include "utils.h"
#include "nafigator.h"


/**
 * The root folder where the tiles are stored
 */
char root_dir[] = "shared/misc/Nafigator";


/**
 * @brief Display the dialog with the specified message
 *
 * @return none
 */
void dialog_show_message(app_control_t *pApp, char *msg) {
    if(pApp->dialog) {
        dialog_set_alert_message_text(pApp->dialog, msg);
        dialog_update(pApp->dialog);
        dialog_show(pApp->dialog);
    }
}

/**
 * @brief Cleanup of the dialog
 *
 * @return none
 */
void dialog_cleanup(app_control_t *pApp) {
    if(pApp->dialog) {
        dialog_destroy(pApp->dialog);
    }
    pApp->dialog = NULL;
}

/**
 * @brief Initialisation of the dialog
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int dialog_init(app_control_t *pApp)
{
    if(dialog_create_alert(&(pApp->dialog)) != BPS_SUCCESS) {
        LOG_ERR("Failed to create dialog, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(dialog_add_button(pApp->dialog, "Hide", true, NULL, true) != BPS_SUCCESS) {
        LOG_ERR("Failed to add dialog button, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

/**
 * @brief Handler of the geolocation's events
 *
 * @return none
 */
void handle_geolocation_event(app_control_t *pApp, bps_event_t *event)
{
    static geo_data_t geo_data;
    static log_record_t record;

    double latitude;
    double longitude;
    double speed;
    double altitude;
    double heading;
    int num_sat;

    switch(bps_event_get_code(event)) {
        case GEOLOCATION_INFO:
            if(geolocation_event_is_latitude_valid(event) && geolocation_event_is_longitude_valid(event)) {
                latitude = geolocation_event_get_latitude(event);
                longitude = geolocation_event_get_longitude(event);

                geo_data.position.x = (int)(((longitude + 180.0) / 360.0) * (1 << (MAX_ZOOM + TILE_ZOOM)));
                geo_data.position.y = (int)((1 - log(tan(latitude * M_PI / 180.0) + 1 / cos(latitude * M_PI / 180.0)) / M_PI) *
                    (1 << (MAX_ZOOM + TILE_ZOOM - 1)));

                record.latitude = (sint32_t)(latitude * 1e7);
                record.longitude = (sint32_t)(longitude * 1e7);
                record.position_accuracy = (sint16_t)geolocation_event_get_accuracy(event);

                record.flags = 0;

                if(geolocation_event_is_utc_time_valid(event)) {
                    record.utc = (uint32_t)(geolocation_event_get_utc_time(event) / 1000LL);
                    record.flags |= GEO_IS_VALID_UTC;
                } else {
                    record.utc = time(NULL);
                }

                if(geolocation_event_is_speed_valid(event)) {
                    speed = geolocation_event_get_speed(event);
                    geo_data.speed = (int)(speed * 3.6);
                    record.speed = (sint32_t)(speed * 1e3);
                    record.flags |= GEO_IS_VALID_SPEED;
                }

                if(geolocation_event_is_altitude_valid(event)) {
                    altitude = geolocation_event_get_altitude(event);
                    geo_data.altitude = (int)altitude;
                    record.altitude = (sint16_t)altitude;
                    record.flags |= GEO_IS_VALID_ALTITUDE;
                }

                if(geolocation_event_is_altitude_accuracy_valid(event)) {
                    record.altitude_accuracy = (sint16_t)geolocation_event_get_altitude_accuracy(event);
                    record.flags |= GEO_IS_VALID_ALT_ACCUR;
                }

                if(geolocation_event_is_heading_valid(event)) {
                    heading = geolocation_event_get_heading(event);
                    geo_data.heading = (int)heading;
                    record.heading = (uint16_t)(heading * 1e2);
                    record.flags |= GEO_IS_VALID_HEADING;
                }

                if(geolocation_event_is_num_satellites_valid(event)) {
                    num_sat = geolocation_event_get_num_satellites_used(event);
                    geo_data.num_sat = num_sat;
                    record.num_sat = (uint16_t)num_sat;
                    record.flags |= GEO_IS_VALID_NUM_SAT;
                }

                // save the record to the geolocation log
                if(fwrite(&record, 1, sizeof(record), pApp->log) != sizeof(record)) {
                    LOG_WRN("Failed to write geolocation log record");
                    dialog_show_message(pApp, "Failed to write geolocation log!");
                }

                // send signal to the renderer thread
                pthread_mutex_lock(&(pApp->rend.mutex));
                pApp->state.geo_data = geo_data;
                pApp->state.is_geo_valid = 1;
                pthread_cond_signal(&(pApp->rend.cond));
                pthread_mutex_unlock(&(pApp->rend.mutex));
            }
            break;
    }
}

/**
 * @brief Unsubscribe geolocation events
 *
 * @return none
 */
void geolocation_events_unsubscribe(app_control_t *pApp)
{
    if(pApp->is_geolocation_init) {
        geolocation_stop_events(0);
        pApp->is_geolocation_init = 0;

        if(pApp->log) {
            fclose(pApp->log);
            pApp->log = NULL;
        }
    }
}

/**
 * @brief Subscribe and configure geolocation events
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int geolocation_events_subscribe(app_control_t *pApp)
{
    if(geolocation_request_events(0) != BPS_SUCCESS) {
        LOG_ERR("Error requesting geolocation events, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }
    pApp->is_geolocation_init = 1;

    // Get geolocation events once a second, which is the most often that they
    // are capable of being reported.
    geolocation_set_period(1);

    // Get geolocation events when the app goes to backgrownd
    geolocation_set_background(true);

    // Optimize power usage in case the device's physical location is not changing
    geolocation_set_stationary(true);

#if 0 //@TODO: do I really need this?
    // Use only satellites to get the geolocation
    if(geolocation_set_provider(GEOLOCATION_PROVIDER_GNSS) != BPS_SUCCESS) {
        LOG_ERR("Error set geolocation provider, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }
#endif

    time_t t = time(NULL);
    char path[128];
    log_header_t header;

    memcpy(path, root_dir, sizeof(root_dir) - 1);
    strftime(path + sizeof(root_dir) - 1, sizeof(path) - (sizeof(root_dir) - 1), "/%F_%H%M%S.naf", localtime(&t));

    LOG_DBG("Opening geolocation log: \"%s\"", path);

    pApp->log = fopen(path, "wb");
    if(!pApp->log) {
        LOG_ERR("Failed to create geolocation log: \"%s\"", path);
        return EXIT_FAILURE;
    }

    memcpy(header.signature, LOG_HEADER_SIGNATURE, sizeof(header.signature));
    header.version = LOG_HEADER_VERSION;
    header.header_size = sizeof(log_header_t);
    header.record_size = sizeof(log_record_t);

    if(fwrite(&header, 1, sizeof(log_header_t), pApp->log) != sizeof(log_header_t)) {
        LOG_ERR("Failed to write %d bytes of the geolocation log header", sizeof(log_header_t));
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

/**
 * @brief Handler of the navigator's events
 *
 * @return none
 */
void handle_navigator_event(app_control_t *pApp, bps_event_t *event)
{
    switch (bps_event_get_code(event)) {
        case NAVIGATOR_EXIT:
            LOG_INF("event NAVIGATOR_EXIT");
            pApp->is_app_exit = 1;
            break;

        case NAVIGATOR_WINDOW_ACTIVE:
            LOG_INF("event NAVIGATOR_WINDOW_ACTIVE");
            pthread_mutex_lock(&(pApp->rend.mutex));
            pApp->state.is_active = 1;
            pthread_mutex_unlock(&(pApp->rend.mutex));
            break;

        case NAVIGATOR_WINDOW_INACTIVE:
            LOG_INF("event NAVIGATOR_WINDOW_INACTIVE");
            pthread_mutex_lock(&(pApp->rend.mutex));
            pApp->state.is_active = 0;
            pthread_mutex_unlock(&(pApp->rend.mutex));
            break;

        default:
            break;
    }
}

/**
 * @brief Handler of the screen's events
 *
 * @return none
 */
void handle_screen_event(app_control_t *pApp, bps_event_t *event)
{
    int type;
    screen_window_t window;
    screen_event_t screen_event = screen_event_get_event(event);

    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &type);

    switch (type) {
        case SCREEN_EVENT_MTOUCH_TOUCH:
            screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void **)&window);
            if(window == pApp->win_zoom_in.wndh) {
                LOG_DBG("Zoom in tapped");
                if(pApp->state.zoom < MAX_ZOOM) {
                    pthread_mutex_lock(&(pApp->rend.mutex));
                    pApp->state.zoom++;
                    pthread_cond_signal(&(pApp->rend.cond));
                    pthread_mutex_unlock(&(pApp->rend.mutex));
                }
            } else if(window == pApp->win_zoom_out.wndh) {
                LOG_DBG("Zoom out tapped");
                if(pApp->state.zoom > MIN_ZOOM) {
                    pthread_mutex_lock(&(pApp->rend.mutex));
                    pApp->state.zoom--;
                    pthread_cond_signal(&(pApp->rend.cond));
                    pthread_mutex_unlock(&(pApp->rend.mutex));
                }
            }
            break;

        default:
            break;
    }
}

/**
 * @brief Unsubscribe events and shutdown BPS
 *
 * @return none
 */
void events_unsubscribe(app_control_t *pApp)
{
    if(pApp->is_bps_init) {
        geolocation_events_unsubscribe(pApp);

        if(pApp->is_screen_init) {
            screen_stop_events(pApp->ctxh);
            pApp->is_screen_init = 0;
        }

        if(pApp->is_navigator_init) {
            navigator_stop_events(0);
            pApp->is_navigator_init = 0;
        }
    }
}

/**
 * @brief Initialisation of the BPS and subscription to the selected evens
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int events_subscribe(app_control_t *pApp)
{
    if(navigator_request_events(0) != BPS_SUCCESS) {
        LOG_ERR("Failed to subscribe navigator events, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }
    pApp->is_navigator_init = 1;

    if(screen_request_events(pApp->ctxh) != BPS_SUCCESS) {
        LOG_ERR("Failed to subscribe screen events, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }
    pApp->is_screen_init = 1;

    if(geolocation_events_subscribe(pApp) != BPS_SUCCESS) {
        LOG_ERR("Failed to subscribe geolocation events");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

/**
 * @brief Post the specified buffer into the screen
 *
 * @return none
 */
void win_main_post(app_control_t *pApp, buffer_t *buf)
{
    // Set the window viewport
    int position[2] = { pApp->win_main.cur_pos.x - pApp->win_main.buf_pos.x - (pApp->win_main.w >> 1),
                        pApp->win_main.cur_pos.y - pApp->win_main.buf_pos.y - (pApp->win_main.h >> 1) };
    screen_set_window_property_iv(pApp->win_main.wndh, SCREEN_PROPERTY_SOURCE_POSITION, position);

    // Refresh the window on the screen
    int dirty_rects[4] = {0, 0, pApp->win_main.w, pApp->win_main.h};
    screen_post_window(pApp->win_main.wndh, buf->bufh, 1, (const int*)dirty_rects, 0);
}

/**
 * @brief Load the tile at the specified position
 *        into the specified buffer of the main application window
 *
 * @return none
 */
void win_main_load_tile(app_control_t *pApp, buffer_t *buf, int_coord_t *tile, int_coord_t *pix)
{
    char path[128];

    snprintf(path, sizeof(path), "%s/%d/%d/%d", root_dir, pApp->win_main.cur_zoom, tile->x, tile->y);
    LOG_DBG("loading \"%s\" at %d,%d", path, pix->x, pix->y);

    img_t img;
    img.flags = IMG_FORMAT | IMG_DIRECT | IMG_W | IMG_H;
    img.format = IMG_FMT_PKHE_ARGB8888;
    img.w = TILE_SIZE;
    img.h = TILE_SIZE;
    img.access.direct.stride = buf->stride;
    img.access.direct.data = buf->data;

    img.access.direct.data += pix->x * 4; // 4 is "bytes per pixel" for ARGB8888 format
    img.access.direct.data += pix->y * buf->stride;

    int ret;
    if((ret = img_load_file(pApp->imglib, path, NULL, &img)) != IMG_ERR_OK) {
        LOG_INF("failed to load %s, err=%d", path, ret);
        int attribs[] =  {
            SCREEN_BLIT_COLOR,  ALPHA_SOLID | CLR_TILE_FAILED,
            SCREEN_BLIT_DESTINATION_X, pix->x,
            SCREEN_BLIT_DESTINATION_Y, pix->y,
            SCREEN_BLIT_DESTINATION_WIDTH, TILE_SIZE,
            SCREEN_BLIT_DESTINATION_HEIGHT, TILE_SIZE,
            SCREEN_BLIT_END};
        screen_fill(pApp->ctxh, buf->bufh, attribs);
    }
}

/**
 * @brief Full update of the main application window
 *
 *        Completely reload all the tiles in case of the
 *        first window update, zoom change and other
 *        situations where the new data could not be
 *        derived from the current state
 *
 * @return none
 */
void win_main_full_update(app_control_t *pApp)
{
    int_coord_t tiles;  // top left corner of the shadow buffer measured in tiles of the current zoom level
    int_coord_t tile;   // currently loading tile measured in tiles of the current zoom level
    int_coord_t pix;    // currently loading tile measured in pixels within the shadow buffer
    buffer_t *shadow_buf = pApp->win_main.buf + pApp->win_main.shadow_idx;

    // Calculate initial position to load the tiles in tiles units
    tiles.x = (pApp->win_main.cur_pos.x >> TILE_ZOOM) - (shadow_buf->w >> (TILE_ZOOM + 1));
    tiles.y = (pApp->win_main.cur_pos.y >> TILE_ZOOM) - (shadow_buf->h >> (TILE_ZOOM + 1));

    // Calculate position of the shadow buffer
    pApp->win_main.buf_pos.x = tiles.x << TILE_ZOOM;
    pApp->win_main.buf_pos.y = tiles.y << TILE_ZOOM;

    LOG_DBG("tiles: %d,%d buf_pos: %d,%d", tiles.x, tiles.y, pApp->win_main.buf_pos.x, pApp->win_main.buf_pos.y);

    // Load the tiles into shadow buffer
    for(pix.x = 0, tile.x = tiles.x; pix.x < shadow_buf->w; pix.x += TILE_SIZE, tile.x++) {
        for(pix.y = 0, tile.y = tiles.y; pix.y < shadow_buf->h; pix.y += TILE_SIZE, tile.y++) {
            win_main_load_tile(pApp, shadow_buf, &tile, &pix);
        }
    }

    win_main_post(pApp, shadow_buf);

    pApp->win_main.last_pos = pApp->win_main.cur_pos;
    pApp->win_main.last_zoom = pApp->win_main.cur_zoom;

    // Swap the front and the shadow buffer
    pApp->win_main.shadow_idx ^= 1;
}

/**
 * @brief Partial update of the main application window
 *
 *        When possible, use the loaded tiles in front buffer.
 *        Load only the missing tiles.
 *
 * @return none
 */
void win_main_partial_update(app_control_t *pApp)
{
    int_coord_t tiles;      // top left corner of the shadow buffer measured in tiles of the current zoom level
    int_coord_t shadow_pos; // new position of the shadow buffer. Same units as win_main.shadow_pos
    int_coord_t d;          // coordinates delta
    int_coord_t tile;       // currently loading tile measured in tiles of the current zoom level
    int_coord_t pix;        // currently loading tile measured in pixels within the shadow buffer
    buffer_t *shadow_buf = pApp->win_main.buf + pApp->win_main.shadow_idx;
    buffer_t *front_buf = pApp->win_main.buf + (pApp->win_main.shadow_idx ^ 1);

    if(pApp->win_main.last_pos.x == pApp->win_main.cur_pos.x && pApp->win_main.last_pos.y == pApp->win_main.cur_pos.y) {
        LOG_DBG("same position, no need to update");
        return;
    }

    d.x = pApp->win_main.cur_pos.x - pApp->win_main.buf_pos.x;
    d.y = pApp->win_main.cur_pos.y - pApp->win_main.buf_pos.y;
    LOG_DBG("cur_pos offset from win_main.shadow_pos: %d,%d", d.x, d.y);

    if(d.x >= (pApp->win_main.w >> 1) && d.x < front_buf->w - (pApp->win_main.w >> 1) &&
       d.y >= (pApp->win_main.h >> 1) && d.y < front_buf->h - (pApp->win_main.h >> 1)) {
        // no need to load any new tiles, just post the window with the new viewport offset
        win_main_post(pApp, front_buf);
        pApp->win_main.last_pos = pApp->win_main.cur_pos;
        pApp->win_main.last_zoom = pApp->win_main.cur_zoom;
        return;
    }

    // Calculate position to load the tiles in tiles units
    tiles.x = (pApp->win_main.cur_pos.x >> TILE_ZOOM) - (shadow_buf->w >> (TILE_ZOOM + 1));
    tiles.y = (pApp->win_main.cur_pos.y >> TILE_ZOOM) - (shadow_buf->h >> (TILE_ZOOM + 1));

    // Calculate new position of the shadow buffer
    shadow_pos.x = tiles.x << TILE_ZOOM;
    shadow_pos.y = tiles.y << TILE_ZOOM;

    d.x = shadow_pos.x - pApp->win_main.buf_pos.x;
    d.y = shadow_pos.y - pApp->win_main.buf_pos.y;
    LOG_DBG("new tiles: %d,%d buf_pos: %d,%d delta: %d,%d", tiles.x, tiles.y, shadow_pos.x, shadow_pos.y, d.x, d.y);

    if(d.x >= shadow_buf->w || d.x <= -shadow_buf->w ||
       d.y >= shadow_buf->h || d.y <= -shadow_buf->h) {
        // no tiles in shadow buffer could be re-used, perform full update
        LOG_DBG("dx=%d dy=%d perform full update", d.x, d.y);
        win_main_full_update(pApp);
        return;
    }

    int_coord_t src;
    int_coord_t dst;
    int_coord_t sz;

    if(d.x >= 0) {
        src.x = d.x;
        dst.x = 0;
        sz.x = shadow_buf->w - d.x;
    } else {
        src.x = 0;
        dst.x = -d.x;
        sz.x = shadow_buf->w + d.x;
    }

    if(d.y >= 0) {
        src.y = d.y;
        dst.y = 0;
        sz.y = shadow_buf->h - d.y;
    } else {
        src.y = 0;
        dst.y = -d.y;
        sz.y = shadow_buf->h + d.y;
    }
    LOG_DBG("shadow buffer blit from %d,%d to %d,%d size %d,%d", src.x, src.y, dst.x, dst.y, sz.x, sz.y);

    int attribs[] =  {
        SCREEN_BLIT_SOURCE_X, src.x,
        SCREEN_BLIT_SOURCE_Y, src.y,
        SCREEN_BLIT_SOURCE_WIDTH, sz.x,
        SCREEN_BLIT_SOURCE_HEIGHT, sz.y,
        SCREEN_BLIT_DESTINATION_X, dst.x,
        SCREEN_BLIT_DESTINATION_Y, dst.y,
        SCREEN_BLIT_DESTINATION_WIDTH, sz.x,
        SCREEN_BLIT_DESTINATION_HEIGHT, sz.y,
        SCREEN_BLIT_END};
    if(screen_blit(pApp->ctxh, shadow_buf->bufh, front_buf->bufh, attribs) != 0) {
        LOG_WRN("shadow buffer blit failed");
    }

    pApp->win_main.buf_pos.x = shadow_pos.x;
    pApp->win_main.buf_pos.y = shadow_pos.y;

    // Load the missing tiles into the shadow buffer
    for(pix.x = 0, tile.x = tiles.x; pix.x < shadow_buf->w; pix.x += TILE_SIZE, tile.x++) {
        for(pix.y = 0, tile.y = tiles.y; pix.y < shadow_buf->h; pix.y += TILE_SIZE, tile.y++) {
            if(d.x >= 0 && d.y >= 0) {
                if(pix.x >= shadow_buf->w - d.x || pix.y >= shadow_buf->h - d.y) {
                    LOG_DBG("dx=%d dy=%d", d.x, d.y);
                    win_main_load_tile(pApp, shadow_buf, &tile, &pix);
                }
            } else if(d.x >= 0 && d.y < 0) {
                if(pix.x >= shadow_buf->w - d.x || pix.y < - d.y) {
                    LOG_DBG("dx=%d dy=%d", d.x, d.y);
                    win_main_load_tile(pApp, shadow_buf, &tile, &pix);
                }
            } else if(d.x < 0 && d.y >= 0) {
                if(pix.x < - d.x || pix.y >= shadow_buf->h - d.y) {
                    LOG_DBG("dx=%d dy=%d", d.x, d.y);
                    win_main_load_tile(pApp, shadow_buf, &tile, &pix);
                }
            } else { // if(d.x < 0 && d.y < 0)
                if(pix.x < - d.x || pix.y < - d.y) {
                    LOG_DBG("dx=%d dy=%d", d.x, d.y);
                    win_main_load_tile(pApp, shadow_buf, &tile, &pix);
                }
            }
        }
    }

    win_main_post(pApp, shadow_buf);

    pApp->win_main.last_pos = pApp->win_main.cur_pos;
    pApp->win_main.last_zoom = pApp->win_main.cur_zoom;

    // Swap the front and the shadow buffer
    pApp->win_main.shadow_idx ^= 1;
}

/**
 * @brief Update of the main application window
 *
 * @return none
 */
void win_main_update(app_control_t *pApp)
{
    // convert the position at MAX_ZOOM to the position at current zoom
    pApp->win_main.cur_pos.x >>= MAX_ZOOM - pApp->win_main.cur_zoom;
    pApp->win_main.cur_pos.y >>= MAX_ZOOM - pApp->win_main.cur_zoom;

    if(pApp->win_main.cur_zoom != pApp->win_main.last_zoom) {
        win_main_full_update(pApp);
    } else {
        win_main_partial_update(pApp);
    }
}

/**
 * @brief Shutdown of the main application window
 *
 * @return none
 */
void win_main_cleanup(app_control_t *pApp)
{
    if(pApp->win_main.wndh) {
        // destroy window buffer
        if(pApp->win_main.buf[0].bufh) {
            screen_destroy_window_buffers(pApp->win_main.wndh);
            pApp->win_main.buf[0].bufh = NULL;
        }

        screen_destroy_window(pApp->win_main.wndh);
        pApp->win_main.wndh = NULL;
    }
}

/**
 * @brief Initialisation of the main application window
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int win_main_init(app_control_t *pApp)
{
    int format;
    int size[2];

    //=====================================================================================================
    // Initialisation of the main window
    //=====================================================================================================

    const char *env = getenv("WIDTH");
    if(!env) {
        LOG_ERR("Failed to get screen width");
        return EXIT_FAILURE;
    }
    pApp->win_main.w = atoi(env);

    env = getenv("HEIGHT");
    if(!env) {
        LOG_ERR("Failed to get screen height");
        return EXIT_FAILURE;
    }
    pApp->win_main.h = atoi(env);

    LOG_INF("Window size: %dx%d", pApp->win_main.w, pApp->win_main.h);

    if(screen_create_window(&(pApp->win_main.wndh), pApp->ctxh) != 0) {
        LOG_ERR("Failed to create main application window, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_create_window_group(pApp->win_main.wndh, get_window_group_id()) != 0) {
        LOG_ERR("Failed to create window group, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int idle_mode = SCREEN_IDLE_MODE_KEEP_AWAKE;
    if(screen_set_window_property_iv(pApp->win_main.wndh, SCREEN_PROPERTY_IDLE_MODE, &idle_mode) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_IDLE_MODE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int usage = SCREEN_USAGE_NATIVE | SCREEN_USAGE_WRITE;
    if(screen_set_window_property_iv(pApp->win_main.wndh, SCREEN_PROPERTY_USAGE, &usage) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_USAGE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    format = SCREEN_FORMAT_RGBA8888;
    if(screen_set_window_property_iv(pApp->win_main.wndh, SCREEN_PROPERTY_FORMAT, &format) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_FORMAT, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int zorder = ZORDER_WIN_MAIN;
    if(screen_set_window_property_iv(pApp->win_main.wndh, SCREEN_PROPERTY_ZORDER, &zorder) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_ZORDER, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    size[0] = pApp->win_main.w;
    size[1] = pApp->win_main.h;
    if(screen_set_window_property_iv(pApp->win_main.wndh, SCREEN_PROPERTY_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_set_window_property_iv(pApp->win_main.wndh, SCREEN_PROPERTY_SOURCE_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_SOURCE_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    // the size of the buffer is the size of the window, aligned to TILE_SIZE + 2 tiles
    pApp->win_main.buf[0].w = pApp->win_main.buf[1].w = (((pApp->win_main.w + TILE_SIZE - 1) >> TILE_ZOOM) + 2) << TILE_ZOOM;
    pApp->win_main.buf[0].h = pApp->win_main.buf[1].h = (((pApp->win_main.h + TILE_SIZE - 1) >> TILE_ZOOM) + 2) << TILE_ZOOM;
    LOG_INF("Buffer size: %dx%d", pApp->win_main.buf[0].w, pApp->win_main.buf[0].h);

    size[0] = pApp->win_main.buf[0].w;
    size[1] = pApp->win_main.buf[0].h;
    if(screen_set_window_property_iv(pApp->win_main.wndh, SCREEN_PROPERTY_BUFFER_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_BUFFER_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_create_window_buffers(pApp->win_main.wndh, 2) != 0) {
        LOG_ERR("Failed to create window buffers, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    screen_buffer_t bufh[2];
    if(screen_get_window_property_pv(pApp->win_main.wndh, SCREEN_PROPERTY_RENDER_BUFFERS, (void*)&bufh) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_RENDER_BUFFERS, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }
    pApp->win_main.buf[0].bufh = bufh[0];
    pApp->win_main.buf[1].bufh = bufh[1];

    if(screen_get_buffer_property_pv(pApp->win_main.buf[0].bufh, SCREEN_PROPERTY_POINTER, (void*)&(pApp->win_main.buf[0].data)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_POINTER, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_iv(pApp->win_main.buf[0].bufh, SCREEN_PROPERTY_STRIDE, (void*)&(pApp->win_main.buf[0].stride)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_STRIDE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_pv(pApp->win_main.buf[1].bufh, SCREEN_PROPERTY_POINTER, (void*)&(pApp->win_main.buf[1].data)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_POINTER, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_iv(pApp->win_main.buf[1].bufh, SCREEN_PROPERTY_STRIDE, (void*)&(pApp->win_main.buf[1].stride)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_STRIDE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int attribs[] = {
        SCREEN_BLIT_COLOR, ALPHA_SOLID | CLR_DEFAULT_BGRND,
        SCREEN_BLIT_END};
    screen_fill(pApp->ctxh, pApp->win_main.buf[0].bufh, attribs);

    int dirty_rects[4] = {0, 0, pApp->win_main.w, pApp->win_main.h};
    if(screen_post_window(pApp->win_main.wndh, pApp->win_main.buf[0].bufh, 1, (const int*)dirty_rects, 0) != 0) {
        LOG_ERR("Failed to post window, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    pApp->win_main.shadow_idx = 1;

    return EXIT_SUCCESS;
}

/**
 * @brief Post the specified string to the status bar
 *
 * @return none
 */
void win_stat_post(app_control_t *pApp, char *status)
{
    int cur_off = 0;    // current X-offset (pixels) in the window buffer, where the character to be rendered
    int chr_w;          // current rendering character width (pixels)
    int i;

    for(i = 0; status[i] != '\0'; i++) {
        int c = status[i] - ' ';

        if(c < 0 || c >= FONT_NUM_CHARS) {
            c = 0; // bad character, replace by space
        }

        chr_w = pApp->win_stat.chr_off[c + 1] - pApp->win_stat.chr_off[c];
        if(cur_off + chr_w >= pApp->win_stat.buf.w) {
            break; // string too long, discard the rest
        }

        // Blit the character bitmap into the window buffer
        int attribs[] =  {
            SCREEN_BLIT_SOURCE_X, pApp->win_stat.chr_off[c],
            SCREEN_BLIT_SOURCE_Y, 0,
            SCREEN_BLIT_SOURCE_WIDTH, chr_w,
            SCREEN_BLIT_SOURCE_HEIGHT, FONT_HEIGHT,
            SCREEN_BLIT_DESTINATION_X, cur_off,
            SCREEN_BLIT_DESTINATION_Y, (STAT_WINDOW_HEIGHT - FONT_HEIGHT) / 2,
            SCREEN_BLIT_DESTINATION_WIDTH, chr_w,
            SCREEN_BLIT_DESTINATION_HEIGHT, FONT_HEIGHT,
            SCREEN_BLIT_END};
        screen_blit(pApp->ctxh, pApp->win_stat.buf.bufh, pApp->win_stat.font.buf.bufh, attribs);

        cur_off += chr_w;
    }

    // Refresh the status bar on the screen
    int dirty_rects[4] = {0, 0, pApp->win_stat.buf.w, pApp->win_stat.buf.h};
    screen_post_window(pApp->win_stat.wndh, pApp->win_stat.buf.bufh, 1, (const int*)dirty_rects, 0);
}

/**
 * @brief Update of the status bar window
 *
 * @return none
 */
void win_stat_update(app_control_t *pApp)
{
    char status[128];

    if(pApp->win_stat.cur.speed == pApp->win_stat.last.speed &&
       pApp->win_stat.cur.altitude == pApp->win_stat.last.altitude &&
       pApp->win_stat.cur.heading == pApp->win_stat.last.heading &&
       pApp->win_stat.cur.num_sat == pApp->win_stat.last.num_sat &&
       pApp->win_stat.cur.zoom == pApp->win_stat.last.zoom) {
        LOG_DBG("same params, no need to update");
        return;
    }

    snprintf(status, sizeof(status), " %d km/h   %dm   %d'   SAT:%d   Zoom:%d            ",
        pApp->win_stat.cur.speed,
        pApp->win_stat.cur.altitude,
        pApp->win_stat.cur.heading,
        pApp->win_stat.cur.num_sat,
        pApp->win_stat.cur.zoom - MIN_ZOOM);

    LOG_DBG("Status: \"%s\"", status);

    win_stat_post(pApp, status);

    pApp->win_stat.last = pApp->win_stat.cur;
}

/**
 * @brief Shutdown of the status bar window
 *
 * @return none
 */
void win_stat_cleanup(app_control_t *pApp)
{
    if(pApp->win_stat.wndh) {
        // destroy font buffer
        if(pApp->win_stat.font.pxmh) {
            if(pApp->win_stat.font.buf.bufh) {
                screen_destroy_pixmap_buffer(pApp->win_stat.font.pxmh);
                pApp->win_stat.font.buf.bufh = NULL;
            }
            screen_destroy_pixmap(pApp->win_stat.font.pxmh);
            pApp->win_stat.font.pxmh = NULL;
        }

        // destroy window buffer
        if(pApp->win_stat.buf.bufh) {
            screen_destroy_window_buffers(pApp->win_stat.wndh);
            pApp->win_stat.buf.bufh = NULL;
        }

        screen_destroy_window(pApp->win_stat.wndh);
        pApp->win_stat.wndh = NULL;
    }
}

/**
 * @brief Initialisation of the status bar window
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int win_stat_init(app_control_t *pApp)
{
    int format;
    int size[2];
    int position[2];

    //=====================================================================================================
    // Initialisation of the status bar window
    //=====================================================================================================

    pApp->win_stat.buf.w = pApp->win_main.w;
    pApp->win_stat.buf.h = STAT_WINDOW_HEIGHT;

    LOG_INF("Window size: %dx%d", pApp->win_stat.buf.w, pApp->win_stat.buf.h);

    if(screen_create_window_type(&(pApp->win_stat.wndh), pApp->ctxh, SCREEN_CHILD_WINDOW) != 0) {
        LOG_ERR("Failed to create status bar window, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_join_window_group(pApp->win_stat.wndh, get_window_group_id()) != 0) {
        LOG_ERR("Failed to join window group, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int usage = SCREEN_USAGE_NATIVE | SCREEN_USAGE_WRITE;
    if(screen_set_window_property_iv(pApp->win_stat.wndh, SCREEN_PROPERTY_USAGE, &usage) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_USAGE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    format = SCREEN_FORMAT_RGBA8888;
    if(screen_set_window_property_iv(pApp->win_stat.wndh, SCREEN_PROPERTY_FORMAT, &format) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_FORMAT, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int zorder = ZORDER_WIN_STAT;
    if(screen_set_window_property_iv(pApp->win_stat.wndh, SCREEN_PROPERTY_ZORDER, &zorder) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_ZORDER, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    size[0] = pApp->win_stat.buf.w;
    size[1] = pApp->win_stat.buf.h;
    if(screen_set_window_property_iv(pApp->win_stat.wndh, SCREEN_PROPERTY_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_set_window_property_iv(pApp->win_stat.wndh, SCREEN_PROPERTY_SOURCE_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_SOURCE_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_set_window_property_iv(pApp->win_stat.wndh, SCREEN_PROPERTY_BUFFER_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_BUFFER_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    position[0] = 0;
    position[1] = pApp->win_main.h - pApp->win_stat.buf.h;
    if(screen_set_window_property_iv(pApp->win_stat.wndh, SCREEN_PROPERTY_POSITION, position) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_POSITION, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_create_window_buffers(pApp->win_stat.wndh, 1) != 0) {
        LOG_ERR("Failed to create window buffers, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_window_property_pv(pApp->win_stat.wndh, SCREEN_PROPERTY_RENDER_BUFFERS, (void*)&(pApp->win_stat.buf.bufh)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_RENDER_BUFFERS, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_pv(pApp->win_stat.buf.bufh, SCREEN_PROPERTY_POINTER, (void*)&(pApp->win_stat.buf.data)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_POINTER, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_iv(pApp->win_stat.buf.bufh, SCREEN_PROPERTY_STRIDE, (void*)&(pApp->win_stat.buf.stride)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_STRIDE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int attribs[] = {
        SCREEN_BLIT_COLOR, ALPHA_TRANSPARENT,
        SCREEN_BLIT_END};
    screen_fill(pApp->ctxh, pApp->win_stat.buf.bufh, attribs);

    //=====================================================================================================
    // Initialisation of the font buffer
    //=====================================================================================================

    pApp->win_stat.font.buf.w = FONT_WIDTH;
    pApp->win_stat.font.buf.h = FONT_HEIGHT;
    LOG_INF("Font buffer size: %dx%d", pApp->win_stat.font.buf.w, pApp->win_stat.font.buf.h);

    if(screen_create_pixmap(&(pApp->win_stat.font.pxmh), pApp->ctxh) != 0) {
        LOG_ERR("Failed to create pixmap for font, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    format = SCREEN_FORMAT_RGBA8888;
    if(screen_set_pixmap_property_iv(pApp->win_stat.font.pxmh, SCREEN_PROPERTY_FORMAT, &format) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_FORMAT for font, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    size[0] = pApp->win_stat.font.buf.w;
    size[1] = pApp->win_stat.font.buf.h;
    if(screen_set_pixmap_property_iv(pApp->win_stat.font.pxmh, SCREEN_PROPERTY_BUFFER_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_BUFFER_SIZE for font, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_create_pixmap_buffer(pApp->win_stat.font.pxmh) != 0) {
        LOG_ERR("Failed to create window buffer for font, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_pixmap_property_pv(pApp->win_stat.font.pxmh, SCREEN_PROPERTY_RENDER_BUFFERS, (void*)&(pApp->win_stat.font.buf.bufh)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_RENDER_BUFFERS for font, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_pv(pApp->win_stat.font.buf.bufh, SCREEN_PROPERTY_POINTER, (void*)&(pApp->win_stat.font.buf.data)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_POINTER for font, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_iv(pApp->win_stat.font.buf.bufh, SCREEN_PROPERTY_STRIDE, (void*)&(pApp->win_stat.font.buf.stride)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_STRIDE for font, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    // load the font from the file
    img_t img;
    img.flags = IMG_FORMAT | IMG_DIRECT | IMG_W | IMG_H;
    img.format = IMG_FMT_PKHE_ARGB8888;
    img.w = pApp->win_stat.font.buf.w;
    img.h = pApp->win_stat.font.buf.h;
    img.access.direct.stride = pApp->win_stat.font.buf.stride;
    img.access.direct.data = pApp->win_stat.font.buf.data;

    int ret;
    if((ret = img_load_file(pApp->imglib, FONT_BITMAP_PATH, NULL, &img)) != IMG_ERR_OK) {
        LOG_ERR("failed to load font \"%s\", err=%d", FONT_BITMAP_PATH, ret);
        return EXIT_FAILURE;
    }

    // load the table of character offsets (for proportional font)
    FILE *f;
    if((f = fopen(FONT_CHAR_OFFS_PATH, "rt")) == NULL) {
        LOG_ERR("failed to open font char offsets \"%s\"", FONT_CHAR_OFFS_PATH);
        return EXIT_FAILURE;
    }

    int i;
    for(i = 0; i <= FONT_NUM_CHARS; i++) {
        fscanf(f, "%d", pApp->win_stat.chr_off + i);
    }
    fclose(f);

    win_stat_post(pApp, "Looking for satellites...");

    return EXIT_SUCCESS;
}

/**
 * @brief Shutdown of the zoom button window
 *
 * @return none
 */
void win_zoom_cleanup(app_control_t *pApp, win_zoom_t *win_zoom)
{
    if(win_zoom->wndh) {
        // destroy window buffer
        if(win_zoom->buf.bufh) {
            screen_destroy_window_buffers(win_zoom->wndh);
            win_zoom->buf.bufh = NULL;
        }

        screen_destroy_window(win_zoom->wndh);
        win_zoom->wndh = NULL;
    }
}

/**
 * @brief Initialisation of the zoom button window
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int win_zoom_init(app_control_t *pApp, win_zoom_t *win_zoom, int_coord_t *pos, char *img_path)
{
    int format;
    int size[2];
    int position[2];

    //=====================================================================================================
    // Initialisation of the zoom button window
    //=====================================================================================================

    win_zoom->buf.w = WIN_ZOOM_WIDTH;
    win_zoom->buf.h = WIN_ZOOM_HEIGHT;

    LOG_INF("Window size: %dx%d", win_zoom->buf.w, win_zoom->buf.h);

    if(screen_create_window_type(&(win_zoom->wndh), pApp->ctxh, SCREEN_CHILD_WINDOW) != 0) {
        LOG_ERR("Failed to create zoom button window, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_join_window_group(win_zoom->wndh, get_window_group_id()) != 0) {
        LOG_ERR("Failed to join window group, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int usage = SCREEN_USAGE_NATIVE;
    if(screen_set_window_property_iv(win_zoom->wndh, SCREEN_PROPERTY_USAGE, &usage) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_USAGE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    format = SCREEN_FORMAT_RGBA8888;
    if(screen_set_window_property_iv(win_zoom->wndh, SCREEN_PROPERTY_FORMAT, &format) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_FORMAT, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int zorder = ZORDER_WIN_ZOOM;
    if(screen_set_window_property_iv(win_zoom->wndh, SCREEN_PROPERTY_ZORDER, &zorder) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_ZORDER, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    size[0] = win_zoom->buf.w;
    size[1] = win_zoom->buf.h;
    if(screen_set_window_property_iv(win_zoom->wndh, SCREEN_PROPERTY_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_set_window_property_iv(win_zoom->wndh, SCREEN_PROPERTY_SOURCE_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_SOURCE_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_set_window_property_iv(win_zoom->wndh, SCREEN_PROPERTY_BUFFER_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_BUFFER_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    position[0] = pos->x;
    position[1] = pos->y;
    if(screen_set_window_property_iv(win_zoom->wndh, SCREEN_PROPERTY_POSITION, position) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_POSITION, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_create_window_buffers(win_zoom->wndh, 1) != 0) {
        LOG_ERR("Failed to create window buffers, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_window_property_pv(win_zoom->wndh, SCREEN_PROPERTY_RENDER_BUFFERS, (void*)&(win_zoom->buf.bufh)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_RENDER_BUFFERS, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_pv(win_zoom->buf.bufh, SCREEN_PROPERTY_POINTER, (void*)&(win_zoom->buf.data)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_POINTER, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_iv(win_zoom->buf.bufh, SCREEN_PROPERTY_STRIDE, (void*)&(win_zoom->buf.stride)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_STRIDE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    // load the button image from the file
    img_t img;
    img.flags = IMG_FORMAT | IMG_DIRECT | IMG_W | IMG_H;
    img.format = IMG_FMT_PKHE_ARGB8888;
    img.w = win_zoom->buf.w;
    img.h = win_zoom->buf.h;
    img.access.direct.stride = win_zoom->buf.stride;
    img.access.direct.data = win_zoom->buf.data;

    int ret;
    if((ret = img_load_file(pApp->imglib, img_path, NULL, &img)) != IMG_ERR_OK) {
        LOG_ERR("failed to load button image \"%s\", err=%d", img_path, ret);
        return EXIT_FAILURE;
    }

    int dirty_rects[4] = {0, 0, win_zoom->buf.w, win_zoom->buf.h};
    if(screen_post_window(win_zoom->wndh, win_zoom->buf.bufh, 1, (const int*)dirty_rects, 0) != 0) {
        LOG_ERR("Failed to post window, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

/**
 * @brief Shutdown of the vehicle icon window
 *
 * @return none
 */
void win_vehicle_cleanup(app_control_t *pApp)
{
    if(pApp->win_vehicle.wndh) {
        // destroy window buffer
        if(pApp->win_vehicle.buf.bufh) {
            screen_destroy_window_buffers(pApp->win_vehicle.wndh);
            pApp->win_vehicle.buf.bufh = NULL;
        }

        screen_destroy_window(pApp->win_vehicle.wndh);
        pApp->win_vehicle.wndh = NULL;
    }
}

/**
 * @brief Initialisation of the vehicle icon window
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int win_vehicle_init(app_control_t *pApp)
{
    int format;
    int size[2];
    int position[2];

    //=====================================================================================================
    // Initialisation of the vehicle icon window
    //=====================================================================================================

    pApp->win_vehicle.w = WIN_VEHICLE_WIDTH;
    pApp->win_vehicle.h = WIN_VEHICLE_HEIGHT;

    LOG_INF("Window size: %dx%d", pApp->win_vehicle.w, pApp->win_vehicle.h);

    if(screen_create_window_type(&(pApp->win_vehicle.wndh), pApp->ctxh, SCREEN_CHILD_WINDOW) != 0) {
        LOG_ERR("Failed to create vehicle icon window, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_join_window_group(pApp->win_vehicle.wndh, get_window_group_id()) != 0) {
        LOG_ERR("Failed to join window group, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int usage = SCREEN_USAGE_NATIVE;
    if(screen_set_window_property_iv(pApp->win_vehicle.wndh, SCREEN_PROPERTY_USAGE, &usage) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_USAGE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    format = SCREEN_FORMAT_RGBA8888;
    if(screen_set_window_property_iv(pApp->win_vehicle.wndh, SCREEN_PROPERTY_FORMAT, &format) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_FORMAT, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    int zorder = ZORDER_WIN_VEHICLE;
    if(screen_set_window_property_iv(pApp->win_vehicle.wndh, SCREEN_PROPERTY_ZORDER, &zorder) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_ZORDER, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    size[0] = pApp->win_vehicle.buf.w = pApp->win_vehicle.w;
    size[1] = pApp->win_vehicle.buf.h = pApp->win_vehicle.h;
    if(screen_set_window_property_iv(pApp->win_vehicle.wndh, SCREEN_PROPERTY_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_set_window_property_iv(pApp->win_vehicle.wndh, SCREEN_PROPERTY_SOURCE_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_SOURCE_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_set_window_property_iv(pApp->win_vehicle.wndh, SCREEN_PROPERTY_BUFFER_SIZE, size) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_BUFFER_SIZE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    position[0] = (pApp->win_main.w - pApp->win_vehicle.w) >> 1;
    position[1] = (pApp->win_main.h - pApp->win_vehicle.h) >> 1;
    if(screen_set_window_property_iv(pApp->win_vehicle.wndh, SCREEN_PROPERTY_POSITION, position) != 0) {
        LOG_ERR("Failed to set SCREEN_PROPERTY_POSITION, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_create_window_buffers(pApp->win_vehicle.wndh, 1) != 0) {
        LOG_ERR("Failed to create window buffers, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_window_property_pv(pApp->win_vehicle.wndh, SCREEN_PROPERTY_RENDER_BUFFERS, (void*)&(pApp->win_vehicle.buf.bufh)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_RENDER_BUFFERS, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_pv(pApp->win_vehicle.buf.bufh, SCREEN_PROPERTY_POINTER, (void*)&(pApp->win_vehicle.buf.data)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_POINTER, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(screen_get_buffer_property_iv(pApp->win_vehicle.buf.bufh, SCREEN_PROPERTY_STRIDE, (void*)&(pApp->win_vehicle.buf.stride)) != 0) {
        LOG_ERR("Failed to get SCREEN_PROPERTY_STRIDE, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    // load the vehicle icon image from the file
    img_t img;
    img.flags = IMG_FORMAT | IMG_DIRECT | IMG_W | IMG_H;
    img.format = IMG_FMT_PKHE_ARGB8888;
    img.w = pApp->win_vehicle.buf.w;
    img.h = pApp->win_vehicle.buf.h;
    img.access.direct.stride = pApp->win_vehicle.buf.stride;
    img.access.direct.data = pApp->win_vehicle.buf.data;

    int ret;
    if((ret = img_load_file(pApp->imglib, WIN_VEHICLE_IMG_PATH, NULL, &img)) != IMG_ERR_OK) {
        LOG_ERR("Failed to load vehicle icon image \"%s\", err=%d", WIN_VEHICLE_IMG_PATH, ret);
        return EXIT_FAILURE;
    }

    int dirty_rects[4] = {0, 0, pApp->win_vehicle.w, pApp->win_vehicle.h};
    if(screen_post_window(pApp->win_vehicle.wndh, pApp->win_vehicle.buf.bufh, 1, (const int*)dirty_rects, 0) != 0) {
        LOG_ERR("Failed to post window, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

/**
 * @brief Shutdown of all graphics subsystems
 *
 * @return none
 */
void screen_cleanup(app_control_t *pApp)
{
    win_vehicle_cleanup(pApp);
    win_zoom_cleanup(pApp, &(pApp->win_zoom_out));
    win_zoom_cleanup(pApp, &(pApp->win_zoom_in));
    win_stat_cleanup(pApp);
    win_main_cleanup(pApp);

    if(pApp->ctxh) {
        screen_destroy_context(pApp->ctxh);
        pApp->ctxh = NULL;
    }
}

/**
 * @brief Initialisation of all graphics subsystems
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int screen_init(app_control_t *pApp)
{
    if(screen_create_context(&(pApp->ctxh), SCREEN_APPLICATION_CONTEXT) != 0) {
        LOG_ERR("Failed to create screen context, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }

    if(win_main_init(pApp) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    if(win_stat_init(pApp) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    int_coord_t win_zoom_pos;
    win_zoom_pos.x = pApp->win_main.w - WIN_ZOOM_WIDTH - WIN_ZOOM_WIDTH / 8;
    win_zoom_pos.y = WIN_ZOOM_HEIGHT / 8;
    if(win_zoom_init(pApp, &(pApp->win_zoom_in), &win_zoom_pos, WIN_ZOOM_IN_IMG_PATH) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    win_zoom_pos.y += WIN_ZOOM_HEIGHT + WIN_ZOOM_HEIGHT / 8;
    if(win_zoom_init(pApp, &(pApp->win_zoom_out), &win_zoom_pos, WIN_ZOOM_OUT_IMG_PATH) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    if(win_vehicle_init(pApp) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

/**
 * @brief Shutdown of the image library
 *
 * @return none
 */
void imglib_cleanup(app_control_t *pApp)
{
    if(pApp->imglib) {
        img_lib_detach(pApp->imglib);
        pApp->imglib = NULL;
    }
}

/**
 * @brief Initialisation of the image library
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int imglib_init(app_control_t *pApp)
{
    if(img_lib_attach(&pApp->imglib)) {
        LOG_ERR("Failed to init libimg");
        pApp->imglib = NULL;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

/**
 * @brief Screen rendering thread
 *
 * @return none
 */
static void *renderer_thread(void *arg)
{
    app_control_t *pApp = (app_control_t *)arg;

    LOG_INF("thread entered");

    pthread_mutex_lock(&(pApp->rend.mutex));
    while(1) {
        pthread_cond_wait(&(pApp->rend.cond), &(pApp->rend.mutex));
        if(pApp->rend.is_thread_exit) {
            LOG_INF("thread signalled to exit");
            break;
        }

        if(!pApp->state.is_active || !pApp->state.is_geo_valid) {
            // do not process events if the application is inactive
            // or until got a valid geolocation
            LOG_DBG("skip signal");
            continue;
        }

        // update params for the main window
        pApp->win_main.cur_pos = pApp->state.geo_data.position;
        pApp->win_main.cur_zoom = pApp->state.zoom;

        // update params for the status window
        pApp->win_stat.cur.speed = pApp->state.geo_data.speed;
        pApp->win_stat.cur.altitude = pApp->state.geo_data.altitude;
        pApp->win_stat.cur.heading = pApp->state.geo_data.heading;
        pApp->win_stat.cur.num_sat = pApp->state.geo_data.num_sat;
        pApp->win_stat.cur.zoom = pApp->state.zoom;
        LOG_DBG("thread signalled, x=%d y=%d", pApp->win_main.cur_pos.x, pApp->win_main.cur_pos.y);

        pthread_mutex_unlock(&(pApp->rend.mutex));
        win_main_update(pApp);
        win_stat_update(pApp);
        pthread_mutex_lock(&(pApp->rend.mutex));
    }
    pthread_mutex_unlock(&(pApp->rend.mutex));

    LOG_INF("thread exit");

    return NULL;
}

/**
 * @brief Shutdown of the screen rendering thread
 *
 * @return none
 */
void renderer_cleanup(app_control_t *pApp)
{
    if(pApp->rend.thread) {
        pthread_mutex_lock(&(pApp->rend.mutex));
        pApp->rend.is_thread_exit = 1;
        pthread_cond_signal(&(pApp->rend.cond));
        pthread_mutex_unlock(&(pApp->rend.mutex));
        pthread_join(pApp->rend.thread, NULL);
        pApp->rend.thread = NULL;
    }

    if(pApp->rend.is_mutex_init) {
        pthread_mutex_destroy(&(pApp->rend.mutex));
        pApp->rend.is_mutex_init = 0;
    }

    if(pApp->rend.is_cond_init) {
        pthread_cond_destroy(&(pApp->rend.cond));
        pApp->rend.is_cond_init = 0;
    }
}

/**
 * @brief Initialisation of the screen rendering thread
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int renderer_init(app_control_t *pApp)
{
    if(pthread_cond_init(&(pApp->rend.cond), NULL) != EOK) {
        LOG_ERR("Failed to init condvar");
        return EXIT_FAILURE;
    }
    pApp->rend.is_cond_init = 1;

    if(pthread_mutex_init(&(pApp->rend.mutex), NULL) != EOK) {
        LOG_ERR("Failed to init mutex");
        return EXIT_FAILURE;
    }
    pApp->rend.is_mutex_init = 1;

    if(pthread_create(&(pApp->rend.thread), NULL, renderer_thread, pApp) != EOK) {
        LOG_ERR("Failed to start rendering thread");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

/**
 * @brief Shutdown of all application subsystems
 *
 * @return none
 */
void app_cleanup(app_control_t *pApp)
{
    renderer_cleanup(pApp);
    events_unsubscribe(pApp);
    screen_cleanup(pApp);
    imglib_cleanup(pApp);
    dialog_cleanup(pApp);

    if(pApp->is_bps_init) {
        bps_shutdown();
        pApp->is_bps_init = 0;
    }
}

/**
 * @brief Initialisation of all application subsystems
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int app_init(app_control_t *pApp)
{
    pApp->state.zoom = MAX_ZOOM;

    if(bps_initialize() != BPS_SUCCESS) {
        LOG_ERR("Failed to initialize BPS, err=%d: %s", errno, strerror(errno));
        return EXIT_FAILURE;
    }
    pApp->is_bps_init = 1;

    if(dialog_init(pApp) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    if(imglib_init(pApp) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    if(screen_init(pApp) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    if(events_subscribe(pApp) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    if(renderer_init(pApp) != EXIT_SUCCESS) {
        return EXIT_FAILURE;
    }

    LOG_DBG("Application successfully initialized");
    return EXIT_SUCCESS;
}


/**
 * @brief Main application control object
 */
app_control_t app;

/**
 * @brief Application entry point
 *
 * @return EXIT_SUCCESS or EXIT_FAILURE
 */
int main(void)
{
    if(app_init(&app) != EXIT_SUCCESS) {
        LOG_ERR("Initialization failed!");
        app_cleanup(&app);
        return EXIT_FAILURE;
    }

    while(!app.is_app_exit) {
        bps_event_t *event = NULL;

        if(bps_get_event(&event, -1) != BPS_SUCCESS) {
            LOG_ERR("Failed to retrieve event!");
            break;
        }

        if(event) {
            int domain = bps_event_get_domain(event);

            if(domain == navigator_get_domain()) {
                handle_navigator_event(&app, event);
            } else if(domain == screen_get_domain()) {
                handle_screen_event(&app, event);
            } else if(domain == geolocation_get_domain()) {
                handle_geolocation_event(&app, event);
            }
        }
    }

    app_cleanup(&app);
    return EXIT_SUCCESS;
}

